/*********************************************************************************
 *      Copyright:  (C) 2025 TBW
 *                  All rights reserved.
 *
 *       Filename:  socket.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(19/05/25)
 *         Author:  huang zixiao <2027838269qq.com>
 *      ChangeLog:  1, Release initial version on "19/05/25 19:53:11"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/un.h>
#include <poll.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/resource.h>

#include "socket.h"
#include "logger.h"

int socket_connect(socket_info_t *socket_info)
{
    struct addrinfo       *result, *rp;
    struct addrinfo        hints;
    int                    rv;
    void                  *addr;
    char                   ipstr[INET6_ADDRSTRLEN];
    struct sockaddr_in    *ipv4;
    struct sockaddr_in6   *ipv6;
    struct timeval         timeout = {1, 0};  // 1 seconds timeout

	if(!socket_info)
	{
		log_error("invalid argument\n");
		goto out;
	}

	socket_close(socket_info);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;      // there is only supported IPV4
    hints.ai_socktype = SOCK_STREAM;  // TCP
    hints.ai_flags = 0;
    hints.ai_protocol = 0;            // Any protocol


    rv = getaddrinfo(socket_info->hostname, socket_info->service, &hints, &result);
    if (rv != 0)
    {
        log_error("getaddrinfo: %s\n", gai_strerror(rv));
		goto out;
    }

    log_info("Address information obtained for%s:%s:\n", socket_info->hostname, socket_info->service);
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        if (rp->ai_family == AF_INET)   // IPv4
        {
            ipv4 = (struct sockaddr_in *)rp->ai_addr;
            addr = &(ipv4->sin_addr);
        }
        else   // IPv6
        {
            ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
            addr = &(ipv6->sin6_addr);
        }

        inet_ntop(rp->ai_family, addr, ipstr, sizeof(ipstr));

        log_info("  Address item:\n");
        log_info("  Address family: %s\n", (rp->ai_family == AF_INET) ? "IPv4" : "IPv6");
        log_info("  socket type: %s\n", (rp->ai_socktype == SOCK_STREAM) ? "TCP" : "UDP");
        log_info("  IP address: %s\n", ipstr);
        log_info("  port: %d\n", ntohs(((struct sockaddr_in *)rp->ai_addr)->sin_port));

        socket_info->fd = socket(rp->ai_family, rp->ai_socktype,
                rp->ai_protocol);
        if (socket_info->fd == -1)
		{
			log_error("create socket failure:%s\n", strerror(errno));
			continue;
		}
        else
        {
            //Connection timeout caused the alarm clock not to be reset,thus affecting sampling.
            setsockopt(socket_info->fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
            setsockopt(socket_info->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
            //setsockopt(socket_info->fd, SOL_SOCKET, SO_SNDBUF, &send_buf_size, optlen);
        }

        if (connect(socket_info->fd, rp->ai_addr, rp->ai_addrlen) != -1)
            break;                  /* Success */

		//log_error("connect failure:%s\n", strerror(errno));
        close(socket_info->fd);
    }

    freeaddrinfo(result);           /* No longer needed */

    if (rp == NULL)
    {               /* No address succeeded */
        log_error("Could not connect:%s\n", strerror(errno));
		goto out;
    }

	socket_info->status = 1;
    return 0;

out:
	socket_info->status = 0;
	socket_info->fd = -1;
	return  -1;
}


int socket_connected(socket_info_t *socket_info)
{
	struct timeval 		timeout = {0, 0};  // 非阻塞
	int                 error_code = 0;
    socklen_t           len = sizeof(error_code);
	char 				buf[1];
	int					n;
	int              	 changed = 0;
	fd_set 				readfds;

	if(!socket_info)
	{
		log_error("invalid argument\n");
		return -1;
	}

	if( socket_info->fd < 0)
	{
		changed = socket_info->status ? 1 : 0;
		socket_info->status = 0;
		goto out;
	}
	//getsockopt(sock->fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);//能及时发现服务器关闭，但有局限
    // 获取套接字错误状态
    if (getsockopt(socket_info->fd, SOL_SOCKET, SO_ERROR, &error_code, &len) == -1)//服务器关闭不能立即发现
    {
        log_error("getsockopt failed:%s\n", strerror(errno));
		changed = socket_info->status ? 1 : 0;
		socket_info->status = 0;
		goto out;
    }

    // 重置错误状态
    if (error_code != 0)
    {
        errno = error_code;  // 设置 errno 以便使用 perror()
        log_error("Socket error:%s\n", strerror(errno));
		changed = socket_info->status ? 1 : 0;
		socket_info->status = 0;
		goto out;
    }

	/* 解决服务器关闭不能及时发现问题 */
	FD_ZERO(&readfds);
	FD_SET(socket_info->fd, &readfds);

	if (select(socket_info->fd + 1, &readfds, NULL, NULL, &timeout) > 0 &&
			FD_ISSET(socket_info->fd, &readfds)) 
	{

		n = recv(socket_info->fd, buf, sizeof(buf), MSG_PEEK);

		if (n == 0) 
		{
			log_error("Connection disconnected:%s\n", strerror(errno));
			changed = socket_info->status ? 1 : 0;
			socket_info->status = 0;
			goto out;
		}
	}

	changed = !socket_info->status ? 1 : 0;
	socket_info->status = 1;//并不需要靠该变量的值去判断连接是否断开,可删？
	//靠fd的值，getsockopt函数
	//用来判断连接状态是否改变，改变则打印调试，避免在while循环里调用sleep

out:
    if( changed )
    {
        log_info("socket status got %s\n", socket_info->status?"connected":"disconnected");
    }
    return socket_info->status;
}

int socket_close(socket_info_t *socket_info)
{
	if(!socket_info)
	{
		log_error("invalid argument\n");
		return -1;
	}

	if(socket_info->fd > 0)
	{
		close(socket_info->fd);
		socket_info->fd = -1;
		socket_info->status = -1;
	}

	return 0;
}

int socket_send(socket_info_t *socket_info, char *data, int bytes)
{
	int            rv = 0;
	int            i = 0;
	int            left_bytes = bytes;//如果一次发不完:缓存区大小不够

	if( !socket_info || !data || bytes<= 0 )
		return -1;

	while( left_bytes > 0 )
	{
		rv = write(socket_info->fd, data+i, left_bytes);
		if( rv < 0 )
		{
			log_info("socket[%d] write() failure: %s, close socket now\n",
					socket_info->fd, strerror(errno));
			socket_close(socket_info);
			return -2;
		}
		else if( rv == left_bytes )
		{
			log_info("socket send %d bytes data over\n", rv);
			return 0;
		}
		else//rv < left_bytes
		{
			/* not send over this time, continue to send left data  */
			i += rv;
			left_bytes -= rv;
			log_info("socket send %d bytes data, left %d bytes data!\n", rv, left_bytes);
			continue;
		}
	}
	return 0;
}
